package org.xbl.xchain.sdk.crypto.algo;

import org.bitcoinj.core.Utils;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.OpenSSHPublicKeySpec;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class Ed25519 extends Algorithm{

    private Ed25519() {
        super(AlgorithmType.ED25519);
    }

    public static Ed25519 getInstance() {
        return Ed25519.InnerSingleton.INSTANCE;
    }

    private static class InnerSingleton {
        private static final Ed25519 INSTANCE = new Ed25519();
    }

    @Override
    public PrivateKey genPrivateKeyFromMnemonic(String mnemonic) throws Exception {
        BigInteger intKey = getBigIntegerFromMnemonic(mnemonic);
        byte[] bytes = intKey.toByteArray();
        KeyFactory keyFactory = KeyFactory.getInstance("Ed25519");
        PrivateKeyInfo privKeyInfo = new PrivateKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), new DEROctetString(Utils.bigIntegerToBytes(intKey, 32)));
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privKeyInfo.getEncoded());
        return keyFactory.generatePrivate(pkcs8KeySpec);
    }

    @Override
    public PrivateKey genPrivateKeyFromPriKeyString(String privateKey) throws Exception {
        BigInteger intKey = new BigInteger(privateKey, 16);
        byte[] bytes = intKey.toByteArray();
        KeyFactory keyFactory = KeyFactory.getInstance("Ed25519");
        PrivateKeyInfo privKeyInfo = new PrivateKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), new DEROctetString(Utils.bigIntegerToBytes(intKey, 32)));
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privKeyInfo.getEncoded());
        return keyFactory.generatePrivate(pkcs8KeySpec);
    }

    @Override
    public AsymmetricKeyParameter genPrivateKeyParameterFromMnemonic(String mnemonic) throws Exception {
        BigInteger intKey = getBigIntegerFromMnemonic(mnemonic);
        Ed25519PrivateKeyParameters privateKeyParameters = new Ed25519PrivateKeyParameters(Utils.bigIntegerToBytes(intKey, 32), 0);
        return privateKeyParameters;
    }

    @Override
    public PublicKey genPublicKey(PrivateKey privateKey) throws Exception {
        KeyFactory keyFactory2 = KeyFactory.getInstance("Ed25519");
        PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());
        ASN1Encodable asn1Encodable = pkInfo.parsePrivateKey();
        DEROctetString derOctetString = (DEROctetString)asn1Encodable;
        BigInteger d2 = new BigInteger(derOctetString.getOctets());
        Ed25519PrivateKeyParameters privateKeyParameters = new Ed25519PrivateKeyParameters(d2.toByteArray(), 0);
        Ed25519PublicKeyParameters publicKeyParameters = privateKeyParameters.generatePublicKey();
        SubjectPublicKeyInfo pubKeyInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), publicKeyParameters.getEncoded());
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKeyInfo.getEncoded());
        return keyFactory2.generatePublic(x509KeySpec);
    }



    @Override
    public AsymmetricKeyParameter genPubKeyParameter(AsymmetricKeyParameter privateKeyParameter) {
        Ed25519PrivateKeyParameters privateKeyParameters = (Ed25519PrivateKeyParameters) privateKeyParameter;
        return privateKeyParameters.generatePublicKey();
    }

    @Override
    public byte[] sign(PrivateKey privateKey, byte[] msg) throws Exception {
        return sign(privateKey, msg, "EdDSA");
    }

    @Override
    public byte[] sign(AsymmetricKeyParameter privateKey, byte[] msg) throws Exception {
        Signer signer = new Ed25519Signer();
        signer.init(true, privateKey);
        signer.update(msg, 0, msg.length);
        return signer.generateSignature();
    }

    @Override
    public String genAddressFromPublicKey(String mainPrefix, PublicKey publicKey) throws Exception {
        byte[] hashBytes;
        byte[] bytes = parsePubKey(publicKey);
        hashBytes = hashDigest(bytes, 0, bytes.length, "SHA-256");
        byte[] md = new byte[20];
        System.arraycopy(hashBytes, 0, md, 0, md.length);
        return org.xbl.xchain.sdk.crypto.encode.Bech32.encode(mainPrefix, encode(0, md));
    }

    @Override
    public boolean verifySignature(PublicKey publicKey, byte[] msg, byte[] signature) throws Exception {
        return verifySignature(publicKey, msg, signature, "EdDSA");
    }

    @Override
    public boolean verifySignature(AsymmetricKeyParameter publicKey, byte[] msg, byte[] signature) {
        Signer verifier = new Ed25519Signer();
        verifier.init(false, publicKey);
        verifier.update(msg, 0, msg.length);
        return verifier.verifySignature(encodeSignature(signature));
    }

    @Override
    byte[] encodeSignature(byte[] signature) {
        return signature;
    }

    @Override
    public byte[] parsePubKey(PublicKey publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("Ed25519");
        OpenSSHPublicKeySpec keySpec = keyFactory.getKeySpec(publicKey, OpenSSHPublicKeySpec.class);
        AsymmetricKeyParameter asymmetricKeyParameter = OpenSSHPublicKeyUtil.parsePublicKey(keySpec.getEncoded());
        Ed25519PublicKeyParameters ed25519PublicKeyParameters = (Ed25519PublicKeyParameters)asymmetricKeyParameter;
        return ed25519PublicKeyParameters.getEncoded();
    }

    @Override
    public byte[] decodeSignature(byte[] derSig){
        return derSig;
    }

}
